package org.gauth4me.core;

/**
 * Implements HMAC calculation algorithm.
 * <p/>
 * HMAC stands for hash-based MAC. Details could be found at
 * <a href="http://tools.ietf.org/html/rfc2104">RFC 2104</a>.
 */
class Hmac implements Mac {
  private HashFunction hash;
  private byte[] iPadding;
  private byte[] oPadding;
  private byte[] key;

  /**
   * Constructs an instance of HMAC-SHA-1 calculator with given key.
   *
   * @param key the initial HMAC secret key
   */
  public Hmac(byte[] key) {
    this(key, new Sha());
  }

  /**
   * Constructs an instance of HMAC calculator with given key and hash function.
   *
   * @param key the initial HMAC secret key
   * @param hashFunction the hash function to be used for calculation
   */
  public Hmac(byte[] key, HashFunction hashFunction) {
    hash = hashFunction;
    setKey(key);
  }

  public void setKey(byte[] key) {
    final int hashBlockSize = hash.getBlockSize();
    if (key.length > hash.getHashSize()) {
      key = hash.hash(key);
    }
    this.key = (byte[]) key.clone();
    iPadding = new byte[hashBlockSize];
    oPadding = new byte[hashBlockSize];
    System.arraycopy(key, 0, iPadding, 0, key.length);
    System.arraycopy(key, 0, oPadding, 0, key.length);
    for (int i = 0; i < hashBlockSize; ++i) {
      iPadding[i] ^= 0x36;
      oPadding[i] ^= 0x5C;
    }
  }

  public byte[] getKey() {
    return (byte[]) key.clone();
  }

  public byte[] sign(byte[] message) {
    return hash.hash(concat(oPadding, hash.hash(concat(iPadding, message))));
  }

  /**
   * Concatenates two byte arrays.
   * 
   * @param array1 the first array
   * @param array2 the second array
   * @return concatenated array (array1 | array2)
   */
  private static byte[] concat(byte[] array1, byte[] array2) {
    byte[] result = new byte[array1.length + array2.length];
    System.arraycopy(array1, 0, result, 0, array1.length);
    System.arraycopy(array2, 0, result, array1.length, array2.length);
    return result;
  }
}
